/** @file
This is the driver that locates the MemoryConfigurationData Variable, if it
exists, and reports the data to the DataHub.

Copyright (c) 2013-2015 Intel Corporation.

SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "MemorySubClass.h"

extern UINT8 MemorySubClassStrings[];

EFI_GUID  gEfiMemorySubClassDriverGuid = EFI_MEMORY_SUBCLASS_DRIVER_GUID;

EFI_STATUS
MemorySubClassEntryPoint (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
/*++

  Routine Description:
    This is the standard EFI driver point that detects whether there is a
    MemoryConfigurationData Variable and, if so, reports memory configuration info
    to the DataHub.

  Arguments:
    ImageHandle   - Handle for the image of this driver
    SystemTable   - Pointer to the EFI System Table

  Returns:
    EFI_SUCCESS if the data is successfully reported
    EFI_NOT_FOUND if the HOB list could not be located.

--*/
{
//  UINT8                           Index;
  UINTN                           DataSize;
  UINT8                           Dimm;
  UINTN                           StringBufferSize;
  UINT8                           NumSlots;
  UINTN                           DevLocStrLen;
  UINTN                           BankLocStrLen;
  UINTN                           ManuStrLen;
  UINTN                           SerialNumStrLen;
  UINTN                           AssertTagStrLen;
  UINTN                           PartNumStrLen;
  UINTN                           MemoryDeviceSize;
  CHAR8*                          OptionalStrStart;
  UINT16                          ArrayInstance;
  UINT64                          DimmMemorySize;
  UINT64                          TotalMemorySize;
  UINT32                          Data;
  UINT32                          MemoryCapacity;
  BOOLEAN                         MemoryDeviceSizeUnitMega;
  EFI_STATUS                      Status;
  EFI_STRING                      StringBuffer;
  EFI_STRING                      DevLocStr;
  EFI_STRING                      BankLocStr;
  EFI_STRING                      ManuStr;
  EFI_STRING                      SerialNumStr;
  EFI_STRING                      AssertTagStr;
  EFI_STRING                      PartNumStr;
  EFI_HII_HANDLE                  HiiHandle;
  EFI_SMBIOS_HANDLE               MemArraySmbiosHandle;
  EFI_SMBIOS_HANDLE               MemArrayMappedAddrSmbiosHandle;
  EFI_SMBIOS_HANDLE               MemDevSmbiosHandle;
  EFI_SMBIOS_HANDLE               MemDevMappedAddrSmbiosHandle;
  EFI_SMBIOS_HANDLE               MemModuleInfoSmbiosHandle;
  SMBIOS_TABLE_TYPE6              *Type6Record;
  SMBIOS_TABLE_TYPE16             *Type16Record;
  SMBIOS_TABLE_TYPE17             *Type17Record;
  SMBIOS_TABLE_TYPE19              *Type19Record;
  SMBIOS_TABLE_TYPE20             *Type20Record;
  EFI_SMBIOS_PROTOCOL             *Smbios;
  EFI_MEMORY_ARRAY_LINK_DATA      ArrayLink;
  EFI_MEMORY_ARRAY_LOCATION_DATA  ArrayLocationData;
  EFI_MEMORY_DEVICE_START_ADDRESS_DATA  DeviceStartAddress;


  DataSize = 0;
  Dimm = 0;


  //
  // Allocate Buffers
  //
  StringBufferSize = (sizeof (CHAR16)) * 100;
  StringBuffer = AllocateZeroPool (StringBufferSize);
  ASSERT (StringBuffer != NULL);

  //
  // Locate dependent protocols
  //
  Status = gBS->LocateProtocol (&gEfiSmbiosProtocolGuid, NULL, (VOID**)&Smbios);
  ASSERT_EFI_ERROR (Status);


  //
  // Add our default strings to the HII database. They will be modified later.
  //
  HiiHandle = HiiAddPackages (
                &gEfiMemorySubClassDriverGuid,
                NULL,
                MemorySubClassStrings,
                NULL
                );
  ASSERT (HiiHandle != NULL);

  //
  // Create physical array and associated data for all mainboard memory
  // This will translate into a Type 16 SMBIOS Record
  //
  ArrayInstance = 1;

  McD0PciCfg32 (QNC_ACCESS_PORT_MCR) = MESSAGE_READ_DW (0x3, 0x8);
  TotalMemorySize =     McD0PciCfg32 (QNC_ACCESS_PORT_MDR);

  ArrayLocationData.MemoryArrayLocation = EfiMemoryArrayLocationSystemBoard;
  ArrayLocationData.MemoryArrayUse = EfiMemoryArrayUseSystemMemory;

  ArrayLocationData.MemoryErrorCorrection = EfiMemoryErrorCorrectionNone;

  Data = 0x40000000;//(UINT32) RShiftU64(MemConfigData->RowInfo.MaxMemory, 10);

  ArrayLocationData.MaximumMemoryCapacity.Exponent = (UINT16) LowBitSet32 (Data);
  ArrayLocationData.MaximumMemoryCapacity.Value    = (UINT16) (Data >> ArrayLocationData.MaximumMemoryCapacity.Exponent);

  NumSlots = 2;// (UINT8)(MemConfigData->RowInfo.MaxRows >> 1);
  ArrayLocationData.NumberMemoryDevices = (UINT16)(NumSlots);

  //
  // Report top level physical array to Type 16 SMBIOS Record
  //
  Type16Record = AllocatePool(sizeof(SMBIOS_TABLE_TYPE16) + 1 + 1);
  ZeroMem(Type16Record, sizeof(SMBIOS_TABLE_TYPE16) + 1 + 1);

  Type16Record->Hdr.Type = EFI_SMBIOS_TYPE_PHYSICAL_MEMORY_ARRAY;
  Type16Record->Hdr.Length = sizeof(SMBIOS_TABLE_TYPE16);
  Type16Record->Hdr.Handle = 0;

  Type16Record->Location = (UINT8)ArrayLocationData.MemoryArrayLocation;

  Type16Record->Use = (UINT8)ArrayLocationData.MemoryArrayUse;

  Type16Record->MemoryErrorCorrection = (UINT8)ArrayLocationData.MemoryErrorCorrection;

  MemoryCapacity = (UINT32) ArrayLocationData.MaximumMemoryCapacity.Value * (1 << ((UINT32) ArrayLocationData.MaximumMemoryCapacity.Exponent - 10));
  Type16Record->MaximumCapacity = MemoryCapacity;

  Type16Record->MemoryErrorInformationHandle = 0xfffe;

  Type16Record->NumberOfMemoryDevices = ArrayLocationData.NumberMemoryDevices;
  //
  // Don't change it. This handle will be referenced by type 17 records
  //
  MemArraySmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = Smbios->Add (Smbios, NULL, &MemArraySmbiosHandle, (EFI_SMBIOS_TABLE_HEADER*) Type16Record);
  FreePool(Type16Record);
  ASSERT_EFI_ERROR (Status);

  // Do  associated data for each DIMM
  //RowConfArray = &MemConfigData->RowConfArray;

  //
  // Get total memory size for the construction of smbios record type 19
  //
  //TotalMemorySize = 0;// MSG_BUS_READ(0x0208);

  //
  // Generate Memory Array Mapped Address info
  //
  Type19Record = AllocatePool(sizeof (SMBIOS_TABLE_TYPE19));
  ZeroMem(Type19Record, sizeof(SMBIOS_TABLE_TYPE19));
  Type19Record->Hdr.Type = EFI_SMBIOS_TYPE_MEMORY_ARRAY_MAPPED_ADDRESS;
  Type19Record->Hdr.Length = sizeof(SMBIOS_TABLE_TYPE19);
  Type19Record->Hdr.Handle = 0;
  Type19Record->StartingAddress = 0;
  Type19Record->EndingAddress = (UINT32)RShiftU64(TotalMemorySize, 10) - 1;
  Type19Record->MemoryArrayHandle = MemArraySmbiosHandle;
  Type19Record->PartitionWidth = (UINT8)(NumSlots);

  //
  // Generate Memory Array Mapped Address info (TYPE 19)
  //
  MemArrayMappedAddrSmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = Smbios->Add (Smbios, NULL, &MemArrayMappedAddrSmbiosHandle, (EFI_SMBIOS_TABLE_HEADER*) Type19Record);
  FreePool(Type19Record);
  ASSERT_EFI_ERROR (Status);


  // Use SPD data to generate Device Type info
  ZeroMem (&ArrayLink, sizeof (EFI_MEMORY_ARRAY_LINK_DATA));
  ArrayLink.MemoryDeviceLocator = STRING_TOKEN(STR_MEMORY_SUBCLASS_DEVICE_LOCATOR_0);
  ArrayLink.MemoryBankLocator = STRING_TOKEN(STR_MEMORY_SUBCLASS_DEVICE_LOCATOR_0);
  ArrayLink.MemoryAssetTag = STRING_TOKEN(STR_MEMORY_SUBCLASS_UNKNOWN);
  ArrayLink.MemoryArrayLink.ProducerName = gEfiMemorySubClassDriverGuid;
  ArrayLink.MemoryArrayLink.Instance = ArrayInstance;
  ArrayLink.MemoryArrayLink.SubInstance = EFI_SUBCLASS_INSTANCE_NON_APPLICABLE;
  ArrayLink.MemorySubArrayLink.ProducerName = gEfiMemorySubClassDriverGuid;
  ArrayLink.MemorySubArrayLink.SubInstance = EFI_SUBCLASS_INSTANCE_NON_APPLICABLE;
  ArrayLink.MemoryFormFactor = EfiMemoryFormFactorChip;
  ArrayLink.MemoryType = EfiMemoryTypeDdr2;


  StrCpyS (StringBuffer, StringBufferSize / sizeof (CHAR16), L"NO DIMM,MEMROY DOWN");
  ArrayLink.MemoryManufacturer = HiiSetString (
                                   HiiHandle,
                                   0,
                                   StringBuffer,
                                   NULL
                                   );
  ArrayLink.MemorySerialNumber = HiiSetString (
                                   HiiHandle,
                                   0,
                                   StringBuffer,
                                   NULL
                                   );

  ArrayLink.MemoryPartNumber = HiiSetString (
                                 HiiHandle,
                                 0,
                                 StringBuffer,
                                 NULL
                                 );

  //
  // Hardcode value. Need to revise for different configuration.
  //
  ArrayLink.MemoryTotalWidth = 64;
  ArrayLink.MemoryDataWidth = 64;

  DimmMemorySize = TotalMemorySize;// MSG_BUS_READ(0x0208);

  ArrayLink.MemoryDeviceSize.Exponent = (UINT16) LowBitSet64 (DimmMemorySize);
  ArrayLink.MemoryDeviceSize.Value    = (UINT16) RShiftU64(DimmMemorySize, ArrayLink.MemoryDeviceSize.Exponent);
  ArrayLink.MemoryTypeDetail.Synchronous  = 1;
  Data = 800;
  ArrayLink.MemorySpeed = *((EFI_EXP_BASE10_DATA *) &Data);



  DevLocStr = HiiGetPackageString(&gEfiMemorySubClassDriverGuid, ArrayLink.MemoryDeviceLocator, NULL);
  DevLocStrLen = StrLen(DevLocStr);
  ASSERT(DevLocStrLen <= SMBIOS_STRING_MAX_LENGTH);

  BankLocStr = HiiGetPackageString(&gEfiMemorySubClassDriverGuid, ArrayLink.MemoryBankLocator, NULL);
  BankLocStrLen = StrLen(BankLocStr);
  ASSERT(BankLocStrLen <= SMBIOS_STRING_MAX_LENGTH);

  ManuStr = HiiGetPackageString(&gEfiMemorySubClassDriverGuid, ArrayLink.MemoryManufacturer, NULL);
  ManuStrLen = StrLen(ManuStr);
  ASSERT(ManuStrLen <= SMBIOS_STRING_MAX_LENGTH);

  SerialNumStr = HiiGetPackageString(&gEfiMemorySubClassDriverGuid, ArrayLink.MemorySerialNumber, NULL);
  SerialNumStrLen = StrLen(SerialNumStr);
  ASSERT(SerialNumStrLen <= SMBIOS_STRING_MAX_LENGTH);

  AssertTagStr = HiiGetPackageString(&gEfiMemorySubClassDriverGuid, ArrayLink.MemoryAssetTag, NULL);
  AssertTagStrLen = StrLen(AssertTagStr);
  ASSERT(AssertTagStrLen <= SMBIOS_STRING_MAX_LENGTH);

  PartNumStr = HiiGetPackageString(&gEfiMemorySubClassDriverGuid, ArrayLink.MemoryPartNumber, NULL);
  PartNumStrLen = StrLen(PartNumStr);
  ASSERT(PartNumStrLen <= SMBIOS_STRING_MAX_LENGTH);

  //
  // Report DIMM level memory module information to smbios (Type 6)
  //
  DataSize = sizeof(SMBIOS_TABLE_TYPE6) + DevLocStrLen + 1 + 1;
  Type6Record = AllocatePool(DataSize);
  ZeroMem(Type6Record, DataSize);
  Type6Record->Hdr.Type = EFI_SMBIOS_TYPE_MEMORY_MODULE_INFORMATON;
  Type6Record->Hdr.Length = sizeof (SMBIOS_TABLE_TYPE6);
  Type6Record->Hdr.Handle = 0;
  Type6Record->SocketDesignation = 1;
  if (ArrayLink.MemorySpeed.Value == 0) {
    Type6Record->CurrentSpeed = 0;
  } else {
    //
    // Memory speed is in ns unit
    //
    Type6Record->CurrentSpeed = (UINT8)(1000 / (ArrayLink.MemorySpeed.Value));
  }
  //
  // Device Size
  //
  MemoryDeviceSize = (UINTN)(ArrayLink.MemoryDeviceSize.Value) * (UINTN)(1 << ArrayLink.MemoryDeviceSize.Exponent);
  if (MemoryDeviceSize == 0) {
    *(UINT8*)&(Type6Record->InstalledSize) = 0x7F;
    *(UINT8*)&(Type6Record->EnabledSize)   = 0x7F;
  } else {
    MemoryDeviceSize = (UINTN) RShiftU64 ((UINT64) MemoryDeviceSize, 21);
    while (MemoryDeviceSize != 0) {
      (*(UINT8*)&(Type6Record->InstalledSize))++;
      (*(UINT8*)&(Type6Record->EnabledSize))++;
      MemoryDeviceSize = (UINTN) RShiftU64 ((UINT64) MemoryDeviceSize,1);
    }
  }

  if (ArrayLink.MemoryFormFactor == EfiMemoryFormFactorDimm ||
    ArrayLink.MemoryFormFactor == EfiMemoryFormFactorFbDimm) {
    *(UINT16*)&Type6Record->CurrentMemoryType |= 1<<8;
  }
  if (ArrayLink.MemoryFormFactor == EfiMemoryFormFactorSimm) {
    *(UINT16*)&Type6Record->CurrentMemoryType |= 1<<7;
  }
  if (ArrayLink.MemoryType == EfiMemoryTypeSdram) {
    *(UINT16*)&Type6Record->CurrentMemoryType |= 1<<10;
  }
  if (ArrayLink.MemoryTypeDetail.Edo == 1) {
    *(UINT16*)&Type6Record->CurrentMemoryType |= 1<<4;
  }
  if (ArrayLink.MemoryTypeDetail.FastPaged == 1) {
    *(UINT16*)&Type6Record->CurrentMemoryType |= 1<<3;
  }
  OptionalStrStart = (CHAR8 *)(Type6Record + 1);
  UnicodeStrToAsciiStrS (DevLocStr, OptionalStrStart, DevLocStrLen + 1);
  MemModuleInfoSmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = Smbios->Add (Smbios, NULL, &MemModuleInfoSmbiosHandle, (EFI_SMBIOS_TABLE_HEADER*) Type6Record);
  FreePool(Type6Record);
  ASSERT_EFI_ERROR (Status);
  //
  // Report DIMM level Device Type to smbios (Type 17)
  //
  DataSize = sizeof (SMBIOS_TABLE_TYPE17) + DevLocStrLen + 1 + BankLocStrLen + 1 + ManuStrLen + 1 + SerialNumStrLen + 1 + AssertTagStrLen + 1 + PartNumStrLen + 1 + 1;
  Type17Record = AllocatePool(DataSize);
  ZeroMem(Type17Record, DataSize);
  Type17Record->Hdr.Type = EFI_SMBIOS_TYPE_MEMORY_DEVICE;
  Type17Record->Hdr.Length = sizeof (SMBIOS_TABLE_TYPE17);
  Type17Record->Hdr.Handle = 0;

  Type17Record->MemoryArrayHandle = MemArraySmbiosHandle;
  Type17Record->MemoryErrorInformationHandle = 0xfffe;
  Type17Record->TotalWidth = ArrayLink.MemoryTotalWidth;
  Type17Record->DataWidth = ArrayLink.MemoryDataWidth;
  //
  // Device Size
  //
  MemoryDeviceSize          = ((UINTN) ArrayLink.MemoryDeviceSize.Value) << (ArrayLink.MemoryDeviceSize.Exponent - 10);
  MemoryDeviceSizeUnitMega  = FALSE;
  //
  // kilo as unit
  //
  if (MemoryDeviceSize > 0xffff) {
    MemoryDeviceSize = MemoryDeviceSize >> 10;
    //
    // Mega as unit
    //
    MemoryDeviceSizeUnitMega = TRUE;
  }

  MemoryDeviceSize = MemoryDeviceSize & 0x7fff;
  if (MemoryDeviceSize != 0 && MemoryDeviceSizeUnitMega == FALSE) {
    MemoryDeviceSize |= 0x8000;
  }
  Type17Record->Size = (UINT16)MemoryDeviceSize;

  Type17Record->FormFactor = (UINT8)ArrayLink.MemoryFormFactor;
  Type17Record->DeviceLocator = 1;
  Type17Record->BankLocator = 2;
  Type17Record->MemoryType = (UINT8)ArrayLink.MemoryType;
  CopyMem (
    (UINT8 *) &Type17Record->TypeDetail,
    &ArrayLink.MemoryTypeDetail,
    2
  );

  Type17Record->Speed = ArrayLink.MemorySpeed.Value;
  Type17Record->Manufacturer = 3;
  Type17Record->SerialNumber = 4;
  Type17Record->AssetTag = 5;
  Type17Record->PartNumber = 6;
  //
  // temporary solution for save device label information.
  //
  Type17Record->Attributes = (UINT8)(Dimm + 1);

  OptionalStrStart = (CHAR8 *)(Type17Record + 1);
  UnicodeStrToAsciiStrS (DevLocStr, OptionalStrStart, DevLocStrLen + 1);
  UnicodeStrToAsciiStrS (BankLocStr, OptionalStrStart + DevLocStrLen + 1, BankLocStrLen + 1);
  UnicodeStrToAsciiStrS (ManuStr, OptionalStrStart + DevLocStrLen + 1 + BankLocStrLen + 1, ManuStrLen + 1);
  UnicodeStrToAsciiStrS (SerialNumStr, OptionalStrStart + DevLocStrLen + 1 + BankLocStrLen + 1 + ManuStrLen + 1, SerialNumStrLen + 1);
  UnicodeStrToAsciiStrS (AssertTagStr, OptionalStrStart + DevLocStrLen + 1 + BankLocStrLen + 1 + ManuStrLen + 1 + SerialNumStrLen + 1, AssertTagStrLen + 1);
  UnicodeStrToAsciiStrS (PartNumStr, OptionalStrStart + DevLocStrLen + 1 + BankLocStrLen + 1 + ManuStrLen + 1 + SerialNumStrLen + 1 + AssertTagStrLen + 1, PartNumStrLen + 1);
  MemDevSmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = Smbios->Add (Smbios, NULL, &MemDevSmbiosHandle, (EFI_SMBIOS_TABLE_HEADER*) Type17Record);
  FreePool(Type17Record);
  ASSERT_EFI_ERROR (Status);

  //
  // Generate Memory Device Mapped Address info
  //
  ZeroMem(&DeviceStartAddress, sizeof(EFI_MEMORY_DEVICE_START_ADDRESS_DATA));
  DeviceStartAddress.MemoryDeviceStartAddress = 0;
  DeviceStartAddress.MemoryDeviceEndAddress = DeviceStartAddress.MemoryDeviceStartAddress + DimmMemorySize-1;
  DeviceStartAddress.PhysicalMemoryDeviceLink.ProducerName = gEfiMemorySubClassDriverGuid;
  DeviceStartAddress.PhysicalMemoryDeviceLink.Instance = ArrayInstance;
  DeviceStartAddress.PhysicalMemoryDeviceLink.SubInstance = (UINT16)(Dimm + 1);
  DeviceStartAddress.PhysicalMemoryArrayLink.ProducerName = gEfiMemorySubClassDriverGuid;
  DeviceStartAddress.PhysicalMemoryArrayLink.Instance = ArrayInstance;
  DeviceStartAddress.PhysicalMemoryArrayLink.SubInstance = EFI_SUBCLASS_INSTANCE_NON_APPLICABLE;

  //
  // Single channel mode
  //
  DeviceStartAddress.MemoryDevicePartitionRowPosition = 0x01;
  DeviceStartAddress.MemoryDeviceInterleavePosition = 0x00;
  DeviceStartAddress.MemoryDeviceInterleaveDataDepth = 0x00;

  //
  // Generate Memory Device Mapped Address info (TYPE 20)
  //
  Type20Record = AllocatePool(sizeof (SMBIOS_TABLE_TYPE20));
  ZeroMem(Type20Record, sizeof (SMBIOS_TABLE_TYPE20));
  Type20Record->Hdr.Type = EFI_SMBIOS_TYPE_MEMORY_DEVICE_MAPPED_ADDRESS;
  Type20Record->Hdr.Length = sizeof(SMBIOS_TABLE_TYPE20);
  Type20Record->Hdr.Handle = 0;

  Type20Record->StartingAddress = (UINT32)RShiftU64 (DeviceStartAddress.MemoryDeviceStartAddress, 10);
  Type20Record->EndingAddress = (UINT32)RShiftU64 (DeviceStartAddress.MemoryDeviceEndAddress, 10);
  Type20Record->MemoryDeviceHandle = MemDevSmbiosHandle;
  Type20Record->MemoryArrayMappedAddressHandle = MemArrayMappedAddrSmbiosHandle;
  Type20Record->PartitionRowPosition = DeviceStartAddress.MemoryDevicePartitionRowPosition;
  Type20Record->InterleavePosition = DeviceStartAddress.MemoryDeviceInterleavePosition;
  Type20Record->InterleavedDataDepth = DeviceStartAddress.MemoryDeviceInterleaveDataDepth;
  MemDevMappedAddrSmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = Smbios->Add (Smbios, NULL, &MemDevMappedAddrSmbiosHandle, (EFI_SMBIOS_TABLE_HEADER*) Type20Record);
  FreePool(Type20Record);
  ASSERT_EFI_ERROR (Status);

  return Status;
}
